from pyparsing import *
import string
import box
import Scope
import window
from enum_classes import *

def operatorOperands(tokenlist):
    "generator to extract operators and operands in pairs"
    it = iter(tokenlist)
    while 1:
        try:
            yield (it.next(), it.next())
        except StopIteration:
            break


class AttributeType:
    ACTUAL = 0
    EVENT = 1
    VAR = 2

### Actions

def arithmeticSignOpAction(s, loc, toks):
    sign = toks[0][0]
    val = toks[0][1]
    mult = {'+':1, '-':-1}[sign]
    return mult*val
    
def arithmeticMultOpAction(s, loc, toks):
    value = toks[0]
    prod = value[0]
    for op,val in operatorOperands(value[1:]):
        if op == '*':
            prod*=val
        else:
            prod/=val
    return prod

def arithmeticSumOpAction(s,loc,toks):
    value = toks[0]
    prod = value[0]
    for op,val in operatorOperands(value[1:]):
        if op == '+':
            prod+=val
        else:
            prod-=val
    return prod

def intParseAction(s, loc, toks):
    return int(toks[0])

def floatParseAction(s, loc, toks):
    return float(toks[0])

def varParseAction(s, loc, toks):
    varname = toks[0]
    if Scope.doesVarExists(varname):
        return Scope.get_var(varname)
    else:
        raise TypeError("Unknown Variable: "+varname)

def funcParseAction(s, loc, toks):
    funcname = toks[0]
    in_vars = toks[1:]
    if Scope.doesFuncExists(funcname):
        return Scope.runFunction(funcname,in_vars)
    else:
        raise TypeError("Unknown function: "+funcname)

def questionMarkAction(s, loc, toks):
    return -1

### BNF Dictionary:
          
#Literals:
Comma          = Literal(',')
Underscore     = Literal('_')
LEFT_PAR       = Literal('(')
RIGHT_PAR      = Literal(')')
LEFT_BRAC      = Literal('{')
RIGHT_BRAC     = Literal('}')
EQUALS         = Literal('=')
PLUS           = Literal('+')
MINUS          = Literal('-')
TIMES          = Literal('*')
signOp         = oneOf('+ -')
multOp         = oneOf('* /')
sumOp          = oneOf('+ -')

# Nums:
Digits         = string.digits
Integer        = Word(Digits)
Float          = Combine(Integer + '.' + Optional(Integer, default='0'))
            
# Literals and building blocks:
UCase          = string.uppercase
LCase          = string.lowercase
Letters        = UCase+LCase
VarChars       = Letters + Digits + '_'

# Atoms:
Numeric        = Float | Integer
Variable       = Word(Letters, VarChars)



# Arithmetic
AFuncCall      = Forward()
AInt           = Integer("aint").setParseAction(intParseAction)
AFloat         = Integer("afloat").setParseAction(floatParseAction)
AVariable      = Variable("avar").setParseAction(varParseAction)
ATerm           = AInt | AFloat | AFuncCall | AVariable
ArithExpression = operatorPrecedence( ATerm,
                                          [(signOp, 1, opAssoc.RIGHT, arithmeticSignOpAction),
                                           (multOp, 2, opAssoc.LEFT, arithmeticMultOpAction),
                                           (sumOp, 2, opAssoc.LEFT, arithmeticSumOpAction)
                                           ])
AFuncCallParams = delimitedList(ArithExpression)
AFuncCall << Variable + Literal("(").suppress() + AFuncCallParams + Literal(")").suppress()
AFuncCall.setParseAction(funcParseAction)

EMPTY_TYPE   = Optional(CaselessLiteral("empty"), default="empty").setParseAction(lambda s,loc,toks: box.BoxType.EMPTY)
WINDOW_TYPE  = CaselessLiteral("window").setParseAction(lambda s,loc,toks: box.BoxType.WINDOW)
LABEL_TYPE   = CaselessLiteral("label").setParseAction(lambda s,loc,toks: box.BoxType.LABEL)
TEXTBOX_TYPE = CaselessLiteral("textbox").setParseAction(lambda s,loc,toks: box.BoxType.TEXTBOX)
BUTTON_TYPE  = CaselessLiteral("button").setParseAction(lambda s,loc,toks: box.BoxType.BUTTON)
CHECKBOX_TYPE= CaselessLiteral("checkbox").setParseAction(lambda s,loc,toks: box.BoxType.CHECKBOX)
RADIOBTN_TYPE= CaselessLiteral("radiobutton").setParseAction(lambda s,loc,toks: box.BoxType.RADIOBUTTON)
IMAGE_TYPE   = CaselessLiteral("image").setParseAction(lambda s,loc,toks: box.BoxType.IMAGE)
COMBOBOX_TYPE= CaselessLiteral("combobox").setParseAction(lambda s,loc,toks: box.BoxType.COMBOBOX)
SLIDER_TYPE  = CaselessLiteral("slider").setParseAction(lambda s,loc,toks: box.BoxType.SLIDER)
FRAME_TYPE   = CaselessLiteral("frame").setParseAction(lambda s,loc,toks: box.BoxType.FRAME)
SCROLLAREA_TYPE = CaselessLiteral("scrollarea").setParseAction(lambda s,loc,toks: box.BoxType.SCROLLAREA)
SPLITTER_TYPE= CaselessLiteral("splitter").setParseAction(lambda s,loc,toks: box.BoxType.SPLITTER)

AtomType     = SPLITTER_TYPE | SCROLLAREA_TYPE | FRAME_TYPE | SLIDER_TYPE \
               | COMBOBOX_TYPE | IMAGE_TYPE | RADIOBTN_TYPE | CHECKBOX_TYPE \
               | BUTTON_TYPE | TEXTBOX_TYPE | LABEL_TYPE | WINDOW_TYPE | EMPTY_TYPE

QuestionMarkDimension = Literal("?").setParseAction(questionMarkAction)
ExpressionDimension   = Literal("(").suppress()+ ArithExpression + Literal(")").suppress()
Dimension    = Integer.setParseAction(intParseAction) | QuestionMarkDimension | ExpressionDimension

AtomDef      = Literal("(").suppress()+AtomType+Literal(":").suppress()\
               +Dimension + Literal("x").suppress() + Dimension+Literal(")").suppress()

def atomDefParseAction(s, loc, toks):
    return (toks[0], box.Coordinates(0,0,toks[1],toks[2]))

AtomDef.setParseAction(atomDefParseAction)

NumericAttributeValue = Numeric("value")

printables = string.printable
printables = printables[:63]+printables[64:68]+printables[69:93] #without ",'

ActualStringAttributeValue = ZeroOrMore(Word(printables))
StringAttributeValue = quotedString("string")
ExpressionAttributeValue = Literal("(").suppress()+ ArithExpression + Literal(")").suppress()

def savPA(s,loc,toks):
    return toks[0][1:-1]
StringAttributeValue.setParseAction(savPA)

AttributeValue = StringAttributeValue | NumericAttributeValue | ExpressionAttributeValue

HALIGN_ATTRIBUTE = CaselessLiteral("halign")
VALIGN_ATTRIBUTE = CaselessLiteral("valign")
SCROLLAREA_ATTRIBUTE = CaselessLiteral("scrollArea")
BGCOLOR_ATTRIBUTE = CaselessLiteral("bgcolor")
FGCOLOR_ATTRIBUTE = CaselessLiteral("fgcolor")
ENABLED_ATTRIBUTE = CaselessLiteral("enabled")
TEXT_ATTRIBUTE = CaselessLiteral("text")
VERTICAL_ATTRIBUTE = CaselessLiteral("vertical")
CHECKED_ATTRIBUTE = CaselessLiteral("checked")
IMAGE_ATTRIBUTE = CaselessLiteral("image")
MINVALUE_ATTRIBUTE = CaselessLiteral("minvalue")
MAXVALUE_ATTRIBUTE = CaselessLiteral("maxvalue")
VALUE_ATTRIBUTE = CaselessLiteral("value")
STYLE_ATTRIBUTE = CaselessLiteral("style")

ATTRIBUTE_NAME = HALIGN_ATTRIBUTE|VALIGN_ATTRIBUTE|SCROLLAREA_ATTRIBUTE|BGCOLOR_ATTRIBUTE\
                 |FGCOLOR_ATTRIBUTE|ENABLED_ATTRIBUTE|TEXT_ATTRIBUTE\
                 |VERTICAL_ATTRIBUTE|CHECKED_ATTRIBUTE|IMAGE_ATTRIBUTE\
                 |MINVALUE_ATTRIBUTE|MAXVALUE_ATTRIBUTE|VALUE_ATTRIBUTE\
                 |STYLE_ATTRIBUTE

ActualAttribute    = ATTRIBUTE_NAME +Literal("=").suppress()+AttributeValue  
def actualDefinitionParseAction(s,loc,toks):
    return (AttributeType.ACTUAL,toks[0],toks[1])
ActualAttribute.setParseAction(actualDefinitionParseAction)

ComboBoxDataAttribute = CaselessLiteral("data").suppress()+Literal("=").suppress()+Literal("[").suppress()+delimitedList(quotedString)+Literal("]").suppress()
def comboBoxDataAttributeParseAction(s,loc,toks):
    return (AttributeType.ACTUAL,"data",map(lambda x: x[1:-1],toks))
ComboBoxDataAttribute.setParseAction(comboBoxDataAttributeParseAction)

ONCLICK_EVENT = CaselessLiteral("onclick").setParseAction(lambda s,loc,toks: window.GuiEvents.CLICK)
ONCHECKBOX_CHANGE_EVENT = CaselessLiteral("oncheckbox_change").setParseAction(lambda s,loc,toks: window.GuiEvents.CHECKBOX_CHANGE)
ONRADIOBUTTON_PRESS_EVENT = CaselessLiteral("onradiobutton_press").setParseAction(lambda s,loc,toks: window.GuiEvents.RADIOBUTTON_PRESSED)
ONSLIDE_EVENT = CaselessLiteral("onslide").setParseAction(lambda s,loc,toks: window.GuiEvents.SLIDE)
ONSPLITTER_MOVED_EVENT = CaselessLiteral("onsplit").setParseAction(lambda s,loc,toks: window.GuiEvents.SPLITTER_MOVED)
ONCOMBOBOX_CHANGE_EVENT = CaselessLiteral("onselect").setParseAction(lambda s,loc,toks:GuiEvents.COMBOBOX_CHANGE)

EVENT_NAME=ONCLICK_EVENT|ONCHECKBOX_CHANGE_EVENT|ONRADIOBUTTON_PRESS_EVENT|ONSLIDE_EVENT|ONSPLITTER_MOVED_EVENT|ONCOMBOBOX_CHANGE_EVENT

EventDefinition = EVENT_NAME +Literal("=").suppress()+Literal('"').suppress()+ActualStringAttributeValue + Literal('"').suppress()
def eventDefinitionParseAction(s,loc,toks):
    return (AttributeType.EVENT,toks[0],toks[1])
EventDefinition.setParseAction(eventDefinitionParseAction)

TEXT_VAR = CaselessLiteral("text_var").setParseAction(lambda s,loc,toks: window.GuiVariables.TEXT)
BGCOLOR_VAR = CaselessLiteral("bgcolor_var").setParseAction(lambda s,loc,toks: window.GuiVariables.BGCOLOR)
CHECKBOXVALUE_VAR = CaselessLiteral("checkboxvalue_var").setParseAction(lambda s,loc,toks: window.GuiVariables.CHECKBOX_VAL)
RADIOBUTTONVALUE_VAR = CaselessLiteral("radiobuttonvalue_var").setParseAction(lambda s,loc,toks: window.GuiVariables.RADIOBUTTON_VAL)
HEIGHT_VAR = CaselessLiteral("height_var").setParseAction(lambda s,loc,toks: window.GuiVariables.HEIGHT)
WIDTH_VAR = CaselessLiteral("width_var").setParseAction(lambda s,loc,toks: window.GuiVariables.WIDTH)
X_POS_VAR = CaselessLiteral("x_pos_var").setParseAction(lambda s,loc,toks: window.GuiVariables.X_POS)
Y_POS_VAR = CaselessLiteral("y_pos_var").setParseAction(lambda s,loc,toks: window.GuiVariables.Y_POS)
SLIDERVALUE_VAR = CaselessLiteral("slidervalue_var").setParseAction(lambda s,loc,toks: window.GuiVariables.SLIDER_VAL)
SPLITTERSIZE_VAR = CaselessLiteral("splittersize_var").setParseAction(lambda s,loc,toks: window.GuiVariables.SPLITTER_SIZE)
SETMASK_VAR = CaselessLiteral("setmask_var").setParseAction(lambda s,loc,toks: window.GuiVariables.SETMASK)
SETIMAGE_VAR = CaselessLiteral("setimage_var").setParseAction(lambda s,loc,toks: window.GuiVariables.SETIMAGE)
COMBOBOX_VAR = CaselessLiteral("combobox_var").setParseAction(lambda s,loc,toks: GuiVariables.COMBOBOX_VAL)

VAR_NAME=TEXT_VAR|BGCOLOR_VAR|CHECKBOXVALUE_VAR|RADIOBUTTONVALUE_VAR|HEIGHT_VAR|WIDTH_VAR|X_POS_VAR|Y_POS_VAR|SLIDERVALUE_VAR|SPLITTERSIZE_VAR|SETMASK_VAR|SETIMAGE_VAR|COMBOBOX_VAR

VarDefinition = VAR_NAME + Literal("=").suppress()+ Literal('"').suppress()+ActualStringAttributeValue+ Literal('"').suppress()
def varDefinitionParseAction(s,loc,toks):
    return (AttributeType.VAR,toks[0],toks[1])
VarDefinition.setParseAction(varDefinitionParseAction)



Attribute    = ActualAttribute | EventDefinition | VarDefinition | ComboBoxDataAttribute

Attributes   = Optional(Literal("[").suppress() + Optional(delimitedList(Attribute)) + Literal("]").suppress(), default="[]")
def attributesListParseAction(s,loc,toks):
    atts = {}
    events = {}
    variables = {}
    for tok in toks:
        if tok[0]==AttributeType.EVENT:
            events[tok[2]]=tok[1]
        elif tok[0]==AttributeType.VAR:
            variables[tok[2]]=tok[1]
        else:
            atts[tok[1]]=tok[2]
    return (atts,events,variables)

Attributes.setParseAction(attributesListParseAction)

BoxAtomDef   = AtomDef + Attributes
def boxAtomDefParseAction(s,loc,toks):
    boxtype = toks[0][0]
    coordinates = toks[0][1]
    attributes = toks[1][0]
    events = toks[1][1]
    variables = toks[1][2]
    b = box.Box(coordinates, boxtype, [], [], variables, events, attributes, [])
    return b
BoxAtomDef.setParseAction(boxAtomDefParseAction)

BoxHorizontalCombinator = Literal("|").suppress()
BoxVerticalCombinator = Word("-",min=3).suppress()
BoxHorizontalSplitter = Literal("||").suppress()
BoxVerticalSplitter = Word("=",min=3).suppress()


def horizontalCombinatorParseAction(s, loc, toks):
    boxtype = box.BoxType.EMPTY
    coordinates = box.Coordinates(0,0,-1,-1)
    parent = box.Box(coordinates, boxtype, [],[],{},{},{},[])
    parent.direction = box.BoxDirection.HORIZONTAL
    for child in toks[0]:
        parent.children+=[child]
    return parent

def verticalCombinatorParseAction(s, loc, toks):
    boxtype = box.BoxType.EMPTY
    coordinates = box.Coordinates(0,0,-1,-1)
    parent = box.Box(coordinates, boxtype, [],[],{},{},{},[])
    parent.direction = box.BoxDirection.VERTICAL
    for child in toks[0]:
        parent.children+=[child]
    return parent

def horizontalSplitterParseAction(s, loc, toks):
    boxtype = BoxType.SPLITTER
    coordinates = box.Coordinates(0,0,-1,-1)
    parent = box.Box(coordinates, boxtype,[],[],{},{},{},[])
    for child in toks[0]:
        parent.children+=[child]
    return parent

def verticalSplitterParseAction(s, loc, toks):
    boxtype = BoxType.SPLITTER
    coordinates = box.Coordinates(0,0,-1,-1)
    parent = box.Box(coordinates, boxtype,[],[],{},{},{"vertical":True},[])
    for child in toks[0]:
        parent.children+=[child]
    return parent

BoxName      = Variable("boxname")
def boxNameParseAction(s ,loc,toks):
    boxname = toks[0]
    if Scope.doesBoxExists(boxname):
        return Scope.get_box(boxname)
    else:
        raise TypeError("Unknown box: "+boxname)
BoxName.setParseAction(boxNameParseAction)
    
BoxLoop            = Forward()
UnaryBoxOperator   = Forward()
SingleBoxDef       = BoxAtomDef | BoxName | UnaryBoxOperator | BoxLoop

BoxConcatOperators = operatorPrecedence( SingleBoxDef,
                                          [(BoxHorizontalSplitter, 2, opAssoc.LEFT, horizontalSplitterParseAction),
                                           (BoxHorizontalCombinator, 2, opAssoc.LEFT, horizontalCombinatorParseAction),
                                           (BoxVerticalSplitter, 2, opAssoc.LEFT, verticalSplitterParseAction),
                                           (BoxVerticalCombinator, 2, opAssoc.LEFT, verticalCombinatorParseAction)
                                           ])

UnaryBoxOperator << Literal("(").suppress()+BoxConcatOperators+Literal(":").suppress()\
               +Dimension + Literal("x").suppress() + Dimension+Literal(")").suppress() + Optional(Attributes,default=({},{},{}))
def unaryOperatorParseAction(s,loc,toks):
    box = toks[0]
    w = toks[1]
    h = toks[2]
    attributes = toks[3][0]
    events = toks[3][1]
    variables = toks[3][2]
    box.coordinates.w = w
    box.coordinates.h = h
    for attribute in attributes:
        box.attributes[attribute] = attributes[attribute]
    for event in events:
        box.events[event] = events[event]
    for var in variables:
        box.vars[var] = variables[var]
    return box
UnaryBoxOperator.setParseAction(unaryOperatorParseAction)

HorizontalLoopOperator = Literal("|*").setParseAction(lambda s,loc,toks: BoxDirection.HORIZONTAL)
VerticalLoopOperator = Literal("---*").setParseAction(lambda s,loc,toks: BoxDirection.VERTICAL)
BoxLoopOperator = HorizontalLoopOperator | VerticalLoopOperator

NumericRangeDiscriminator = Integer.setParseAction(intParseAction) + Literal("..").suppress() + Integer.setParseAction(intParseAction)
NumericRangeDiscriminator.setParseAction(lambda s, loc, toks: [range(toks[0],toks[1]+1)])

TextualRangeDiscriminator = Literal("[").suppress() + delimitedList(StringAttributeValue) +Literal("]").suppress()
def textDiscriminatorParseAction(s,loc,toks):
    return [toks]
TextualRangeDiscriminator.setParseAction(textDiscriminatorParseAction)

RangeDiscriminator = NumericRangeDiscriminator | TextualRangeDiscriminator

BoxLoop      << Literal("{").suppress() + SingleBoxDef + BoxLoopOperator + Variable + Literal("=").suppress() + RangeDiscriminator + Literal("}").suppress()
def boxLoopParseAction(s,loc,toks):
    originalbox = toks[0]
    direction = toks[1]
    varname = "@"+toks[2]
    looprange = toks[3]

    coordinates = box.Coordinates(0,0,-1,-1)
    parent = box.Box(coordinates, BoxType.EMPTY, [],[],{},{},{},[])
    parent.direction = direction
    
    for val in looprange:
        newbox = originalbox.createLoopInstance(varname, str(val))
        parent.children+=[newbox]
    return parent
BoxLoop.setParseAction(boxLoopParseAction)


BoxDef       =  BoxConcatOperators

AssignedBoxName = Variable("windowname")
BoxAssignment = AssignedBoxName +Literal("<-").suppress()+BoxDef












    


boxassignmentTest = """
bt_next_month <- (button:40x20)[text = ">> bla!", halign="center", valign="middle"]
    | (button:(f(1,2,3)x?)[text = ">>", halign="center", valign="middle"]
"""

textualTest = """ bt<- (button:40x20)[text = "one two three", halign="center"]                    """

funcTest = """bt <- (button:40x(f(12,34)))[text = ">>"] """

concatTest = """bt <- (button:40x(400*2))[text = ">>", halign="center", text_var="hey"] |(:?x?)[] --- (textbox:50x?)[]"""
splitterTest = """bt <- (button:40x(400*2))[text = ">>", halign="center", text_var="hey"] === (:?x?)[] | (textbox:50x?)[]"""

unaryTest ="""bt <- (  (:?x?)[]|(:?x?)[] : 30x50)[onclick = clickme]  """

boxassignmentTest2 = """
bt_next_month <- (button:40x?)[text = ">>", halign="center", valign="middle", next_month_clicked = ?]
"""

boxassignmentTest3 = """
bt_next_month <- (button:40x?)[text = ">>", halign="center", valign="middle", next_month_clicked = "345"]
"""

looptest = """
bt <- { (label:20x20)[text = "@i"] ---* i=1..7 }
"""

textlooptest = """
bt <- { (label:20x20)[text = "@i"] |* i=["one","two", "three"] }
"""

comboxtest =""" bt<- (combobox:?x?)[data=["one!","twwooo",  "3!"]]"""

test3 = """
top_left_frame <- (
                     (label:?x?)[text = "Color Field"]
                     ---
                     {
                        ( (radiobutton:?x?)[RADIOBUTTON_VAL = "rd_val[@i]", RADIOBUTTON_PRESSED = "rd_toglled[@i]"] | (label:?x?)[text="@i"] | (:?x?)[] )
                        ---* i=["Red", "Green", "Blue"]
                     })
                  :200x200)
                  """

test4 = """
buttom_box <- (
                  ( (checkbox:?x?)[checkboxvalue_var = "check_val" , oncheckbox_change = "checked"] | (:?x?)[] )
                  ---
                  (
                     ( (label:?x?)[bgcolor_var = "color1" , bgcolor = "ff0000"] | (label:?x?)[bgcolor_var = "color2" , bgcolor = "0000ff"] :800x400)
                     ---
                     (:0x0)[]
                  :600x300)
               )
"""

test5 = """
buttom_box <- (
                  ( (checkbox:?x?)[checkboxvalue_var = "check_val" , oncheckbox_change = "checked"] | (:?x?)[] )
                  ---
                  (
                     ( (label:?x?)[bgcolor_var = "color1" , bgcolor = "ff0000"] | (label:?x?)[bgcolor_var = "color2" , bgcolor = "0000ff"] :800x400)
                     ---
                     (:0x0)[]
                  :600x300)
               )

"""

if __name__=='__main__':
   
    test = test4
    
    header = 'Parser Tester begins:\n'
    print header + '=' * (len(header)-1)
    print test
    print '-' * (len(header)-1) + '\n'
    try:
        results = BoxAssignment.parseString(test)
        print '-' * (len(header)-1)
        print results
        print results[1].attributes
    except ParseException, pe:
        print "Parsing failed:"
        print test
        print "%s^" % (' '*(pe.col-1))
        print pe
